<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "https://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="https://www.w3.org/1999/xhtml" xml:lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="zh-CN" />
<!--[if lt IE 9]><style type="text/css">@font-face { font-family: "JinBuGuoWebMono"; src: url("/d/mono.eot"); }</style><![endif]-->
<style media="all" type="text/css">
@font-face { font-family: "JinBuGuoWebMono"; src: url("http://www.jinbuguo.com/d/mono.ttf") format("truetype"); }
* { font-family: "JinBuGuoWebMono", "Ubuntu Mono", "Consolas", "Menlo", monospace; }
body { margin:10px; }
h1,h2 { text-align:center; background:#ddd; }
h2 { margin: 10px 5%; }
h2#auth_name {  background:#fff; }
dt { margin-top: 0.5em; color:#600; }
pre { background: #edc; }
</style>
<title>Sysklogd 系统日志记录器 [金步国]</title>
<script> var _hmt = _hmt || []; (function() { var hm = document.createElement("script"); hm.src = "https://hm.baidu.com/hm.js?d286c55b63a3c54a1e43d10d4c203e75"; var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s); })(); </script>
</head>

<body>
<h1>Sysklogd 系统日志记录器</h1><h2 id="auth_name">作者：<strong><a href="../index.html">金步国</a></strong></h2>
<hr />
<h3>版权声明</h3>
<p>本文作者是一位开源理念的坚定支持者，所以本文虽然不是软件，但是遵照开源的精神发布。</p>
<ul>
<li>无担保：本文作者不保证作品内容准确无误，亦不承担任何由于使用此文档所导致的损失。</li>
<li>自由使用：任何人都可以自由的<u>阅读/链接/打印</u>此文档，无需任何附加条件。</li>
<li>名誉权：任何人都可以自由的<u>转载/引用/再创作</u>此文档，但必须保留作者署名并注明出处。</li>
</ul>
<h3>其他作品</h3>
<p>本文作者十分愿意与他人分享劳动成果，如果你对我的其他翻译作品或者技术文章有兴趣，可以在如下位置查看现有的作品集：</p>
<ul>
<li><a href="../index.html">金步国作品集</a> [ <a href="../index.html">http://www.jinbuguo.com/</a> ]</li>
</ul>
<h3>联系方式</h3>
<p>由于作者水平有限，因此不能保证作品内容准确无误。如果你发现了作品中的错误(哪怕是错别字也好)，请来信指出，任何提高作品质量的建议我都将虚心接纳。</p>
<ul>
<li>Email(QQ)：70171448在QQ邮箱</li>
</ul>
<hr />




<h2>概述</h2>
<p>日志对于系统的重要性不言而喻，比如对于故障诊断和入侵检测，没有日志几乎寸步难行。Linux系统中常见的日志记录器是 <a href="http://www.infodrom.org/projects/sysklogd/download.php">Sysklogd</a> ，当前最新版本是 1.5 。</p>
<p>Sysklogd 日志记录器由两个守护进程(klogd syslogd)和一个配置文件(syslog.conf)组成。klogd 不使用配置文件，它负责截获内核消息，它既可以独立使用也可以作为 syslogd 的客户端运行。syslogd 默认使用 /etc/syslog.conf 作为配置文件，它负责截获应用程序消息，还可以截获 klogd 向其转发的内核消息。支持 internet/unix domain sockets 的特性使得这两个工具可以用于记录本地和远程的日志。</p>
<p></p>
<h3>重要信息</h3>
<ul>
<li>syslogd 守护进程默认情况下并不从 syslog/udp 端口接受任何消息，除非在命令行上使用了"-r"选项。此外，你还应当仔细看看"-l"和"-s"命令行选项。</li>
<li>syslogd 守护进程默认情况下并不转发任何来自远程主机的消息，这是为了避免可能导致的日志无限循环。"-h"选项可以开启转发功能。</li>
<li>syslogd 会剥除来自同一个域范围内的主机中的每条消息中的本地域(local domain)信息。如果你使用了日志分析程序，请将这一特性牢记在心。</li>
<li>syslogd 不会更改任何文件的属性，所以由它创建的文件将是全局可读的。如果你不想这样(比如"auth.*"被进行了记录)，你必须手动事先创建这些文件并设置相应的权限。</li>
<li>如果某些程序发送了大量的日志消息并且导致硬盘非常忙碌，你可以考虑在每一行后面关闭fsync()特性。不过这样可能会导致系统崩溃以后丢失一些日志消息。</li>
<li>如果你使用 init 来直接启动 klogd 或 syslogd ，那么需要在命令行上使用"-n"选项。</li>
<li>如果 System.map 文件存在并且在 klogd 命令行上使用了"-k"的话，那么它可以解码 EIP 地址。这个特性对于诊断系统崩溃非常有用，但是你必须确保 System.map 文件正确无误。</li>
<li>这两个守护进程都会尝试在收到退出信号时删除他们的 .pid 文件，不过如果系统崩溃或者进程被"kill -9"结束，那么可能就会来不及清理。这样，下次启动时就有可能会获得与以前残留的 .pid 文件中的进程号相同的PID，从而导致无法启动(进程号冲突)。解决这个问题的最佳方案是系统的启动脚本(rc.*)自身能够在系统启动的最初就对这些 .pid 文件进行清理(通常是清空 /var/run 目录)。</li>
<li>大文件支持(可以写入大于 2 GB 的日志)并不是 syslogd 的功能，而是 glibc 的功能(使用不同的内核API进行调用)。要启用大文件支持，你必须将 Makefile 中的相应注释取消(两个含有"-D_FILE_OFFSET_BITS"的行中的一个)。</li>
</ul>
<h3>内核的控制台日志等级</h3>
<p>内核的控制台日志等级控制哪些内核消息会在控制台上显示。有两种途径可以修改这个等级，一种是通过内核引导参数，另一种，也是建议的途径是通过 sysctl 来控制，通常这个设置位于 /etc/sysctl.conf 中。比如：</p>
<pre>  kernel.printk = 4 4 1 7</pre>
<p>[注意]控制台日志等级与内核消息等级是不同的概念，默认的内核消息等级(DEFAULT_MESSAGE_LOGLEVEL)是由内核在编译时确定的(CONFIG_DEFAULT_MESSAGE_LOGLEVEL)，其默认值是"4"(WARNING)。而默认的控制台日志等级(DEFAULT_CONSOLE_LOGLEVEL)是"7"(debug)，其含义是等级数字小于等于6的消息(优先级更高)都会显示在控制台上。</p>
<h3>安装注意事项</h3>
<ol>
<li>仔细阅读 README 和 man page 会减少你不少痛苦。目前所有文档都已经放在man page中了。</li>
<li>按照你的需求修改 Makefile 然后再进行编译。如果你没有认真对待第一步，那么可能会编译出不合格的二进制程序。</li>
<li>FSSTND(Linux文件系统标准)要求二进制文件和其配置文件必须位于不同的目录。这个包默认情况下遵守FSSTND的约定。你可以通过修改 Makefile 文件和源代码来修改配置文件的位置。</li>
<li>建议让这两个守护进程都以root身份运行，如果你想了解更多安全方面的问题，请阅读 man page 。</li>
</ol>
<p>下面是 Makefile 文件开头的一部分节选，这部分是在安装前可能需要改动的：</p>
<pre>
CC= gcc
SKFLAGS= $(RPM_OPT_FLAGS) -O3 -DSYSV -fomit-frame-pointer -Wall -fno-strength-reduce
# 启用大文件支持，根据你的系统是32位还是64位进行选择
# -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE
# -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE
# $(shell getconf LFS_SKFLAGS)
LDFLAGS= -s

# 指定 install 程序的位置
INSTALL = /usr/bin/install

# 安装目录
BINDIR = $(prefix)/usr/sbin
MANDIR = $(prefix)/usr/share/man

# 有bug报告说在一个纯 ELF 系统上需要明确指定连接到 libresolv.a 库。
# 如果你在连接 syslogd 时失败，可以试一试取消下面的注释。
# LIBS = /usr/lib/libresolv.a

# 如果你在 ALPHA 平台上连接失败，可以试一试取消下面的注释。
# LIBS = ${LIBS} -linux

# 取消下面的注释可以让 klogd 实现启动延迟，这在 klogd 和 syslogd 并行启动或启动顺序靠的非常近的时候很有用。
# KLOGD_START_DELAY = -DKLOGD_DELAY=5

# 下面的定义表示文件位置遵守FSSTND标准。
FSSTND = -DFSSTND

# 下面的定义指定了 man page 的宿主和权限。
MAN_USER = root
MAN_GROUP = root
MAN_PERMS = 644

# 下面的定义指定了 syslogd 守护进程的 .pid 文件名，
# 源代码(paths.h)中默认的文件名是"syslog.pid"，但很多人认为应当叫"syslogd.pid"。
# 你可以在这里指定你喜欢的名字。
SYSLOGD_PIDNAME = -DSYSLOGD_PIDNAME=\"syslogd.pid\"
</pre>

<h2>syslog.conf</h2>
<p>syslog.conf 是 syslogd 进程的配置文件，将在程序启动时读取，默认位置是 /etc/syslog.conf 。它指定了一系列日志记录规则。规则的格式如下：</p>
<pre>facility.level    action</pre>
<p>这个配置文件中的空白行和以"#"开头的行将被忽略。"facility.level"部分也被称为选择符(seletor)。 seletor 和 action 之间使用一个或多个空白分隔。</p>
<h3>选择符(seletor)</h3>
<p>选择符由 facility 和 level 两部分组成，之间用一个句点(.)连接。这两部分将在后面 syslogd 小节中详细描述。下面提到的名字和 /usr/include/syslog.h 中的 LOG_-values 相一致。</p>
<p>facility 指定了产生日志的子系统，可以是下面的关键字之一：</p>
<pre>
auth		由 pam_pwdb 报告的认证活动。
authpriv	包括私有信息(如用户名)在内的认证活动
cron		与 cron 和 at 有关的信息。
daemon		与 inetd 守护进程有关的信息。
ftp		与 FTP 有关的信息
kern		内核信息，首先通过 klogd 传递。
lpr		与打印服务有关的信息。
mail		与电子邮件有关的信息
mark		syslog 内部功能用于生成时间戳
news		来自新闻服务器的信息
syslog		由 syslog 生成的信息
user		由用户程序生成的信息
uucp		由 uucp 生成的信息
local0 ~ local7	由自定义程序使用，例如使用 local5 做为 ssh 功能
*		通配符代表除了 mark 以外的所有功能
</pre>
<p>在大多数情况下，任何程序都可以通过任何 facility 发送日志消息，但是一般都遵守约定俗成的规则。比如，只有内核才能使用"kern" facility 。</p>
<p>level 指定了消息的优先级，可以是下面的关键字之一(降序排列，严重性越来越低)：</p>
<pre>
emerg	系统不可用
alert	需要立即被修改的条件
crit	阻止某些工具或子系统功能实现的错误条件
err	阻止工具或某些子系统部分功能实现的错误条件
warning	预警信息
notice	具有重要性的普通条件
info	提供信息的消息
debug	不包含函数条件或问题的其他信息
none	没有优先级，通常用于排错
*	除了none之外的所有级别
</pre>
<p>facility 部分可以是用逗号(,)分隔的多个子系统，而多个 seletor 之间也可以通过分号(;)组合在一起。需要注意的是，多个组合在一起的选择符，后面的会覆盖前面的，这样就允许从模式中排除一些优先级。</p>
<p>默认将对指定级别以及更严重级别的消息进行操作，但是可以通过下面2个操作符进行修改。</p>
<p>等于操作符(=)表示仅对这个级别的消息进行操作，不等操作符(!)表示忽略这个级别以及更严重级别的消息。这两个操作符可以同时使用，不过"!"必须出现在"="的前面。</p>
<h3>动作(action)</h3>
<p>这个字段定义了对符合条件的消息进行何种操作，可以选择下列操作之一：</p>
<dl>
<dt>普通文件</dt><dd>将消息记录到这个文件中，必须使用绝对路径。如果在文件名之前加上减号(-)，则表示不将日志信息同步刷新到磁盘上(也就是使用写入缓存)，这样可以提高日志写入性能，但是增加了系统崩溃后丢失日志的风险。</dd>
<dt>命名管道</dt><dd>在绝对路径表示的FIFO文件(使用mkfifo命令创建)前加上管道符号(|)即可。通常用于调试。比如：|/usr/adm/debug</dd>
<dt>终端或者控制台</dt><dd>比如：/dev/tty1 或 /dev/console</dd>
<dt>远程主机</dt><dd>syslogd 能够将消息发送到远程主机或从远程主机接收消息，不过默认并不转发接收到的消息。要将消息发送到远程主机，可以在主机名前加一个"@"即可。</dd>
<dt>逗号分隔的用户名列表</dt><dd>critical 级别的消息除了记录到日志之外，通常还转发到root用户。</dd>
<dt>所有当前登录的用户</dt><dd>如果写上一个星号(*)则表示向当前所有登录的用户显示这条消息。</dd>
</dl>
<h3>实例</h3>
<p>下面的例子来自于实践，希望能够对上面的内容做一个很好的示范以帮助理解。</p>
<pre>
              # 将所有 crit 级别的消息(排除所有内核消息)记录在 critical 文件中
              #
              *.=crit;kern.none            /var/adm/critical

              # 首先记录所有内核消息到 kernel 文件，
              # crit 级别以上的消息转发到远程同时在本地控制台也显示
              # 最后将info(包含)~err(不含)范围的内核消息记录到kernel-info文件(err和更高的级别被忽略)
              #
              kern.*                       /var/adm/kernel
              kern.crit                    @finlandia
              kern.crit                    /dev/console
              kern.info;kern.!err          /var/adm/kernel-info

              # 将恰好等于mail.info的消息显示在第12个终端(tcpd默认使用mail.info)
              #
              mail.=info                   /dev/tty12

              # 将除mail.info之外的所有mail子系统消息记录到mail文件
              #
              mail.*;mail.!=info           /var/adm/mail

              # 将所有mail.info和news.info消息记录到info文件
              #
              mail,news.=info              /var/adm/info

              # 记录所有info和notice级别的消息，来自mail子系统的除外
              #
              *.=info;*.=notice;mail.none  /var/log/messages

              # 记录所有info级别的消息，来自mail和news子系统的除外
              #
              *.=info;mail,news.none       /var/log/messages

              # 向所有登录用户通知emerg级别的消息
              #
              *.=emerg                     *

              # 将所有alert以及更高级别的消息转发到root,joey用户的终端上(如果他们已经登录)
              #
              *.alert                      root,joey

              # 将所有子系统的所有消息都发送到远程名为"finlandia"的主机
              *.*                          @finlandia
</pre>

<h2>syslogd</h2>
<p>syslogd 默认通过 /dev/log 这个 unix domain socket 来接收应用程序发送过来的消息，这个位置是由系统的基本C库决定的。</p>
<p>这个程序的命令行参数如下：</p>
<pre>syslogd  [ -a socket ] [ -f config-file ] [ -h ] [ -l hostlist ] [ -m interval ]
           [ -n ] [ -p socket ] [ -r ] [ -s domainlist ] [ -v ]</pre>
<p>参数说明：</p>
<dl>
<dt>-a socket</dt><dd>指定额外需要监听的 socket ，最多指定19个，可以通过修改 syslogd.c 文件中的 MAXFUNIX 宏修改这个默认值。如果你将某些进程在chroot环境下运行，那么这个选项就很有用了。</dd>
<dt>-f config-file</dt><dd>指定配置文件的位置，默认是 /etc/syslog.conf 。</dd>
<dt>-h</dt><dd>默认情况下 syslogd 并不转发它接收到的远程主机消息。指定这个选项后，进程将会把它接收到的远程主机消息转发到另一个指定的远程主机。</dd>
<dt>-l hostlist</dt><dd>指定一个分号(:)分隔的主机名列表，只记录这些主机的 hostname 而不是全限定域名。</dd>
<dt>-m interval</dt><dd>syslogd 默认每隔20分钟产生一个时间戳标记(-- MARK --)。这个选项用于修改这个默认值。设为零将关闭这个特性。</dd>
<dt>-n</dt><dd>避免自动作为后台进程运行。如果由 init 来直接启动和控制的话这个选项就必须使用。</dd>
<dt>-p socket</dt><dd>你可以指定一个 unix domain socket 来代替默认的 /dev/log [这个位置是由libc决定的]</dd>
<dt>-r</dt><dd>从 internet domain socket 上接收远程消息，也就是监听从514端口上进来的UDP包。 默认不接受任何远程消息。</dd>
<dt>-s domainlist</dt><dd>指定一个分号(:)分隔的域名列表，这些域名在记录前都会被剥除。只能指定完整的域名。比如"-s north.de"并不会剥除"satu.infodrom.north.de"的尾巴，你必须这样写才行：<br />-s north.de:infodrom.north.de</dd>
<dt>-v</dt><dd>打印版本信息后退出。</dd>
</dl>
<h3>信号</h3>
<p>在运行时，syslogd 会将自己的进程号保存在 /var/run/syslogd.pid 文件中。所以可以使用下面的命令向运行中的进程发送信号：</p>
<pre> kill -信号 `cat /var/run/syslogd.pid`</pre>
<dl>
<dt>HUP</dt><dd>使得 syslogd 进程重新初始化。所有打开的文件都会被关闭，然后重新读取配置文件，重新开始记录。</dd>
<dt>TERM</dt><dd>安全退出</dd>
<dt>CHLD</dt><dd>如果当前正在向所有登录的用户发送消息，那么等待这些子进程结束。</dd>
</dl>
<h3>安全问题</h3>
<p>流氓程序可以通过向 syslogd 进程发送大量日志信息来淹没日志文件或者耗尽磁盘空间。下面是一些建议：</p>
<ol>
<li>通过防火墙来限制仅允许某些特定的主机访问 514/UDP socket</li>
<li>将日志文件放在独立的分区上，这样即使磁盘满了也不会有很大的影响。</li>
<li>ext2文件系统可以保留一定的空间仅给root用户使用。这要求将 syslogd 以非root身份运行。</li>
<li>禁止监听 inet domain sockets 将会减少不少风险。</li>
</ol>
<h3>相关文件</h3>
<dl>
<dt>/etc/syslog.conf</dt><dd>syslogd的配置文件</dd>
<dt>/dev/log</dt><dd>默认将从这个 Unix domain socket 读取本地 syslog 消息</dd>
<dt>/var/run/syslogd.pid</dt><dd>包含 syslogd 进程号的文件</dd>
</dl>
<h2>klogd</h2>
<p>klogd 是一个专门截获并记录 Linux 内核消息的守护进程。其命令行语法如下：</p>
<pre>klogd  [ -f file ] [ -iI ] [ -n ] [ -o ] [ -p ] [ -s ] [ -k file ] [ -v ] [ -x ] [ -2 ]</pre>
<p>命令行参数说明：</p>
<dl>
<dt>-f file</dt><dd>将日志直接记录到指定的file中，而不是转发到 syslogd 进程。</dd>
<dt>-i<br />-I</dt><dd>要求当前正在运行的 klogd 守护进程重新装载内核符号表。<br />-i 用于让守护进程重新装载内核模块符号。<br />-I 用于让守护进程重新装载静态内核符号和内核模块符号。</dd>
<dt>-n</dt><dd>禁止自动后台运行，在 klogd 由 init 启动并直接被 init 控制的情况下必须使用此开关。</dd>
<dt>-o</dt><dd>klogd 在读取并记录所有内核消息缓冲区中的消息之后立即退出(不作为守护进程)。</dd>
<dt>-p</dt><dd>只要 klogd 检测到内核消息流中包含了一个 Oops 字符串，那么就重新加载内核符号表。</dd>
<dt>-s</dt><dd>可以通过两个途径获取内核消息: /proc 文件系统和 sys_syslog 系统调用接口。虽然两者本质上完全等价，但 klogd 会优先使用 /proc/kmsg 文件。这个开关则强制 klogd 使用系统调用获取内核消息。</dd>
<dt>-k file</dt><dd>将指定的 file 作为内核符号表文件，也就是System.map文件的位置。</dd>
<dt>-v</dt><dd>打印版本信息后退出。</dd>
<dt>-x</dt><dd>忽略 EIP 转换信息，这样就不需要读取 System.map 文件。</dd>
<dt>-2</dt><dd>当展开符号时打印两行，一行将地址转换为符号，一行是原始文本。这样就允许一些外部程序(比如ksymoops)在原始数据上做一些处理。</dd>
</dl>
<h3>消息转发</h3>
<p>如果 klogd 将内核消息转发给 syslogd 进程，那么它可以分拣出某些特定的消息。原始内核消息的格式如下：</p>
<pre>    &lt;[0-7]&gt;Something said by the kernel.</pre>
<p>尖括号中的数字表示内核消息的优先级，这些数字的定义位于 kernel.h 文件中。当 klogd 收到内核消息之后，将会读取这个数字，并在将此消息转发给 syslogd 时按照这个数字分配适当的优先级。</p>
<p>如果使用 -f 将内核消息直接记录到特定的文件中，那么这条消息将保持原样。</p>
<h3>内核地址解析</h3>
<p>klogd 会尝试将内核地址解析为对应的符号，如果你想得到原始的地址信息，那么可以使用"-2"开关。如果没有使用"-k"选项，那么将会依次尝试下面的路径：</p>
<pre>
       /boot/System.map
       /System.map
       /usr/src/linux/System.map
</pre>
<p>因为内核模块动态加载所以地址并不固定，这时就要使用"-i"/"-I"通知 klogd 内核模块的变化。这两个开关都将导致当前正在运行的 klogd 守护进程重新加载内核符号表。应当在每一次加载或者卸载内核模块后立即运行下列命令：</p>
<pre>       klogd -i</pre>
<p>-p 开关也可以用于更新内核符号表。它会让 klogd 在检测到保护性错误的时候重新加载内核符号表。使用这个开关需要小心，因为操作系统在出现保护性错误(protection fault)的时候已经变得不稳定了，而 klogd 必须执行系统调用才能重新装载内核符号表，所以这样做可能会导致更糟糕的结果。</p>
<h3>控制台日志等级</h3>
<p>内核默认的控制台日志等级(DEFAULT_CONSOLE_LOGLEVEL)是"7"(debug)，也就是等级数字小于等于6的消息(优先级更高)都会显示在控制台上。这些不同等级所代表的意思位于 kernel.h 文件内，这个包内的 syslog.h 中也有一份拷贝。可以使用 sysctl 来指定控制台日志等级，这通常是在 /etc/sysctl.conf 文件中设置的，比如下面这一行：</p>
<pre>              kernel.printk = 4 4 1 7</pre>
<p>就是把内核的控制台日志等级设为了"4"。</p>
<h3>信号处理</h3>
<p>klogd 可以响应8种信号: SIGHUP, SIGINT, SIGKILL, SIGTERM, SIGTSTP, SIGUSR1, SIGUSR2, SIGCONT 。SIGINT, SIGKILL, SIGTERM, SIGHUP 信号会让进程优雅的正常退出。SIGTSTP 信号会让进程停止记录日志并进入休眠状态；SIGCONT 信号会让处于休眠状态的进程重新开始记录日志。组合使用 SIGSTOP 和 SIGCONT 可以在不退出进程的情况下切换日志消息的来源。比如需要卸载 /proc 文件系统的时候，可以使用下面的命令：</p>
<pre>
            # kill -TSTP pid
            # umount /proc
            # kill -CONT pid
</pre>
<p>SIGUSR1 和 SIGUSR2 用于加载/重新加载内核符号表。SIGUSR1 表示重新加载内核模块的符号信息；SIGUSR2 表示同时重新加载模块和静态内核的符号信息。如果 System.map 文件位置正确，那么 SIGUSR1 信号将非常有用。特别是在内核模块改变的时候。</p>
<h3>相关文件</h3>
<dl>
<dt>/proc/kmsg</dt><dd>klogd 默认首选的获取内核消息的来源</dd>
<dt>/var/run/klogd.pid</dt><dd>保存 klogd 的 PID 的文件</dd>
<dt>/boot/System.map, /System.map, /usr/src/linux/System.map</dt><dd>默认搜索的内核符号表位置</dd>
</dl>


<hr />

</body></html>